﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.Threading;

namespace NetTookKits.IOCP
{
    public delegate void RaiseSocketRequest(AsyncSocketClient asyncSocketClient);
    public class AsyncSocketServer
    {
        #region for single
        private static AsyncSocketServer _ins = null;
        private static Dictionary<string, AsyncSocketServer> _insDic = new Dictionary<string,AsyncSocketServer>();
        public static AsyncSocketServer GetInstance(int receiveBuffSize = 1000, int sendBufferSize = 500)
        {
            if (_ins == null)
            {
                _ins = new AsyncSocketServer(receiveBuffSize, sendBufferSize);
            }
            return _ins;
        }
        public static AsyncSocketServer GetInstance(string serverName, int receiveBuffSize = 1000, int sendBufferSize = 500)
        {
            if (!_insDic.ContainsKey(serverName))
            {
                _insDic.Add(serverName, new AsyncSocketServer(receiveBuffSize, sendBufferSize));
            }
            return _insDic[serverName];
        }
        #endregion
        #region events
        public event RaiseSocketRequest ClientReceiveCompleteV;
        public event RaiseSocketRequest ClientConnectedV;
        public event RaiseSocketRequest ClientDisConnectedV;
        #endregion
        #region private data member and constructors
        private int m_receiveBufferSize;
        private int m_sendBufferSize;
        private Socket listenSocket;
        private SocketAsyncEventArgsPool _asyncSocketArgsPool;
        private Thread _gateListenThread;
        private string m_IPAddr;
        private int m_port;
        private AsyncSocketServer(int receiveBuffSize,int sendBufferSize)
        {
            m_receiveBufferSize = receiveBuffSize;
            m_sendBufferSize = sendBufferSize;
            _asyncSocketArgsPool = new SocketAsyncEventArgsPool(10);
            this.extSaeaStack();
        }

        private void extSaeaStack()
        {
            try
            {
                AsyncSocketClient userToken = null;
                for (int i = 0; i < 10; i++)
                {
                    userToken = new AsyncSocketClient(m_receiveBufferSize, m_sendBufferSize);
                    userToken.ReceiveEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
                    _asyncSocketArgsPool.Push(userToken);
                }
            }
            catch (Exception exp)
            {
                throw exp;
            }
        }

        public void startWork(string serverIP,int serverPort)
        {
            m_IPAddr = serverIP;
            m_port = serverPort;
            _gateListenThread = new Thread(_gateListenP);
            _gateListenThread.IsBackground = true;
            _gateListenThread.Start();
        }
        private void _gateListenP()
        {
            IPEndPoint local = new IPEndPoint(IPAddress.Parse(m_IPAddr), m_port);
            startRun(local);
        }
        #endregion

        #region private methods
        private void startRun(IPEndPoint localEndPoint)
        {
            listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            listenSocket.Bind(localEndPoint);
            listenSocket.Listen(100);
            startAccept(null);
        }
        #endregion

        #region communication logic
        //服务器监听
        private void startAccept(SocketAsyncEventArgs acceptEventArg)
        {
            if (acceptEventArg == null)
            {
                acceptEventArg = new SocketAsyncEventArgs();
                acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);
            }
            else
            {
                acceptEventArg.AcceptSocket = null;
            }
            bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);
            if (!willRaiseEvent)
            {
                ProcessAccept(acceptEventArg);
            }
        }
        void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            try
            {
                ProcessAccept(e);
            }
            catch (Exception exp)
            {
                throw exp;
            }
        }
        //处理连接请求
        private void ProcessAccept(SocketAsyncEventArgs e)
        {
            try
            {
                AsyncSocketClient userToken;
                if ((userToken = _asyncSocketArgsPool.Pop()) == null)
                {
                    extSaeaStack();
                    userToken = _asyncSocketArgsPool.Pop();
                }
                userToken.ConnectSocket = e.AcceptSocket;
                userToken.ConnectSocket.SendTimeout = 100;
                if (ClientConnectedV != null)
                {
                    ClientConnectedV(userToken);
                }
                bool delayToEvent = userToken.ConnectSocket.ReceiveAsync(userToken.ReceiveEventArgs);
                if (!delayToEvent)
                {
                    lock (userToken)
                    {
                        ProcessReceive(userToken.ReceiveEventArgs);
                    }
                }

            }
            catch (Exception exp)
            {
                throw exp;
            }
            finally
            {
                startAccept(e);
            }
        }

        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            try
            {
                AsyncSocketClient userToken = e.UserToken as AsyncSocketClient;
                if (userToken.ConnectSocket == null) return;
                if (userToken.ReceiveEventArgs.BytesTransferred > 0 && userToken.ReceiveEventArgs.SocketError == SocketError.Success)
                {
                    #region 接收
                    if (ClientReceiveCompleteV != null)
                    {
                        ClientReceiveCompleteV(userToken);
                    }
                    
                    bool delayToEvent = userToken.ConnectSocket.ReceiveAsync(userToken.ReceiveEventArgs);
                    if (!delayToEvent)
                    {
                        ProcessReceive(userToken.ReceiveEventArgs);
                    }
                    #endregion
                }
                else
                {
                    CloseClientSocket(userToken);
                }
            }
            catch (Exception exp)
            {
                throw exp;
            }
        }
        private bool ProcessSend(SocketAsyncEventArgs e)
        {
            bool ret = false;
            try
            {
                AsyncSocketClient userToken = e.UserToken as AsyncSocketClient;
                if (e.SocketError == SocketError.Success)
                {
                    ret = true;
                }
                else
                {
                    CloseClientSocket(userToken);
                    ret = false;
                }
            }
            catch (Exception exp)
            {
                ret=false;
                throw exp;
            }
            return ret;
        }

        void IO_Completed(object sender, SocketAsyncEventArgs e)
        {
            AsyncSocketClient userToken = e.UserToken as AsyncSocketClient;
            try
            {
                lock (userToken)
                {
                    switch (e.LastOperation)
                    {
                        case SocketAsyncOperation.Receive:
                            ProcessReceive(e);
                            break;
                        case SocketAsyncOperation.Send:
                            ProcessSend(e);
                            break;
                        default:
                            throw new ArgumentException("The last operation completed on the socket was not a receive or send");
                    }
                }
            }
            catch (Exception exp)
            {
                throw exp;
            }

        }
        public void CloseClientSocket(AsyncSocketClient userToken)
        {
            try
            {
                if (ClientDisConnectedV != null)
                {
                    ClientDisConnectedV(userToken);
                }
                if (userToken.ConnectSocket == null)
                {
                    _asyncSocketArgsPool.Push(userToken);
                    return;
                }
                else
                {
                    try
                    {
                        if (userToken.ConnectSocket.Connected)
                            userToken.ConnectSocket.Shutdown(SocketShutdown.Both);
                    }
                    catch (Exception exp)
                    {
                        throw exp;
                    }
                    userToken.ConnectSocket.Close();
                    userToken.ConnectSocket = null;
                    _asyncSocketArgsPool.Push(userToken);
                }
            }
            catch (Exception exp)
            {
                throw exp;
            }
        }
        #endregion
    }
}
